// 
// $Rev:: 12            $:  Revision of last commit
// $Author:: rvhaverm@g#$:  Author of last commit
// $Date:: 2010-07-25 1#$:  Date of last commit
// 
package agents.aglets;

import com.ibm.aglet.*;
import com.ibm.maf.*;
import com.ibm.aglet.system.*;
import com.ibm.aglets.MAFAgentSystem_AgletsImpl;
import com.ibm.aglets.tahiti.Tahiti;

import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Enumeration;
import java.util.Vector;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;

import agents.core.Components;
import agents.exceptions.ExceptionFactory;
import agents.exceptions.AuthenticateException;


/**
 * Through this class that serves as a bridge, users can reach the
 * Aglet-framework.
 * 
 * This class provides Class-methods (keyword static) that interact with the
 * underlying Aglet-framework. It serves as some kind of simplistic bridge.
 * 
 * @author Ruben Van Havermaet
 * @version 0.2
 */
public class AgletBridge
{

    /**
     * The constructor has been made private so that no instantiation is
     * possible.
     */
    private AgletBridge()
    {
    }

    // *** ABOUT INITIALISATION********************************************
    /**
     * This method creates and returns an initialised AgletRuntime.
     * 
     * This method takes care of initialising a runtime-environment in which
     * Aglets can live.
     * 
     * @param username
     *            The name that is responsible for running this AgletRuntime.
     * @param password
     *            The password authenticating username.
     * @return the initialised AgletRuntime that provides a Runtime environment
     *         for Aglets.
     * @see com.ibm.aglet.system.AgletRuntime
     * @see agents.exceptions.AuthenticateException
     */
    public static AgletRuntime createRuntime(String username, String password)
    {

	AgletRuntime rtm = null;
	try
	{
	    // Dynamically load this class
	    Class.forName("org.aglets.log.quiet.QuietInitializer");

	    String[] args = null;
	    rtm = AgletRuntime.init(args);

	    Object obj = rtm.authenticateOwner(username, password);
	    if (obj == null)
	    {
		String msg = "Cannot authenticate the user \"" + username
			+ "\"";

		throw ExceptionFactory.newAuthenticationException(msg);
	    }

	    MAFAgentSystem maf_system = new MAFAgentSystem_AgletsImpl(rtm);
	    String protocol = "atp";
	    protocol = (String) AccessController
		    .doPrivileged(new PrivilegedAction() {
			public Object run()
			{
			    return System.getProperty("maf.protocol", "atp");
			}
		    });

	    MAFAgentSystem.initMAFAgentSystem(maf_system, protocol);
	    Tahiti.initializeGUI();
	    Tahiti.installFactories();
	    Tahiti.installSecurity();
	    MAFAgentSystem.startMAFAgentSystem(maf_system, protocol);
	}
	catch (AuthenticateException e)
	{
	    Components.Instance().getLogger().log(e);
	    System.exit(1);
	}
	catch (Exception e)
	{
	    Components.Instance().getLogger().log("ERROR while creating the AgletRuntime: " + e);
	    System.exit(1);
	}
	return rtm;
    }

    // *** ABOUT CONTEXTS *****************************************************
    /**
     * This method creates a context within the given AgletRuntime.
     * 
     * @param rtm
     *            The AgletRuntime environment in which the context should run.
     * @param name
     *            The name of the AgletContext.
     * @return An AgletContext providing agents with a place to perform tasks.
     * @see com.ibm.aglet.AgletContext
     */
    public static AgletContext createContext(AgletRuntime rtm, String name)
    {
	return rtm.createAgletContext(name);
    }

    /**
     * This method returns the AgletContext in which a given agent is living.
     * 
     * @param agent
     *            an Aglet.
     * @return the AgletContext at the current location of the given agent.
     */
    public static AgletContext getCurrentContext(Aglet agent)
    {
	return agent.getAgletContext();
    }

    /**
     * This method returns the location of the given agent.
     * 
     * @param agent
     *            an Aglet.
     * @return the location (URL) of the given agent.
     */
    public static URL getCurrentLocation(Aglet agent)
    {
	return agent.getAgletContext().getHostingURL();
    }

    /**
     * This method asks an AgletContext to return an AgletProxy.
     * 
     * @param cxt
     *            the AgletContext.
     * @param location
     *            the URL of the agent we want to query.
     * @param id
     *            the AgletID of the agent we want to query.
     * @return the AgletProxy that serves as a representative for the queried
     *         agent.
     * @deprecated
     */
    public static AgletProxy getAgletProxy(AgletContext cxt, URL location,
	    AgletID id)
    {

	AgletProxy result = cxt.getAgletProxy(location, id);
	return result;
    }

    /**
     * This method returns an array of AgletProxies, present at the given
     * AgletContext.
     * 
     * @param cxt
     *            an AgletContext
     * @return an array of AgletProxies
     */
    public static AgletProxy[] getAgletProxies(AgletContext cxt)
    {
	Enumeration e = cxt.getAgletProxies();
	Vector vect = EnumToVector(e);
	AgletProxy[] proxies = new AgletProxy[vect.size()];

	int i = 0;
	while (i < vect.size())
	{
	    AgletProxy p = (AgletProxy) vect.get(i);
	    proxies[i] = p;
	    i++;
	}
	return proxies;
    }

    /*
     * This help-method converts an Enumeration into a Vector. It is used by the
     * getAgletProxies method defined earlier.
     */
    private static Vector EnumToVector(Enumeration e)
    {
	Vector vect = new Vector();
	while (e.hasMoreElements())
	{
	    vect.addElement(e.nextElement());
	}
	return vect;
    }

    // *** ABOUT AGENTS ******************************************************
    /**
     * This method creates an Aglet, specified by its code_base, classname and
     * initialising Object, in the given AgletContext.
     * 
     * @param cxt
     *            The AgletContext in which to create the agent.
     * @param code_base
     *            The URL linking to the agent's code.
     * @param classname
     *            The String providing the fully qualified name of the agent.
     * @param init
     *            The Object responsible for initialising the agent.
     * @return an AgletProxy representing the created agent.
     */
    public static AgletProxy createAgent(AgletContext cxt, URL code_base,
	    String classname, Object init)
    {

	AgletProxy result = null;
	try
	{
	    result = cxt.createAglet(code_base, classname, init);
	}
	catch (IOException e)
	{
	    
	    Components.Instance().getLogger().log("ERROR while creating an agent: IO-error: " + e);
	}
	catch (AgletException e)
	{
	    Components.Instance().getLogger().log("ERROR while creating an agent: " + e);
	}
	catch (ClassNotFoundException e)
	{
	    Components.Instance().getLogger().log("ERROR while creating an agent: Class "
		    + classname + " not found: " + e);
	}
	catch (InstantiationException e)
	{
	    Components.Instance().getLogger().log("ERROR while creating an agent: "
		    + "could not instantiate " + classname + ": " + e);
	}
	return result;
    }

    /**
     * This method returns the Aglet-object behind a given AgletProxy.
     * 
     * @param proxy
     *            The AgletProxy
     * @return the Aglet that is behind the given AgletProxy
     */
    public static Aglet getAglet(AgletProxy proxy)
    {
	Aglet result = null;
	try
	{
	    result = proxy.getAglet();
	}
	catch (InvalidAgletException e)
	{
	    Components.Instance().getLogger().log("ERROR while retrieving the Aglet behind an "
		    + "AgletProxy: " + e.toString());
	}
	return result;
    }

    /**
     * Return the AgletID of a given Aglet.
     * 
     * @param agent
     *            an Aglet
     * @return the AgletID of the given Aglet.
     */
    public static AgletID getAgletID(Aglet agent)
    {
	return agent.getAgletID();
    }

    /**
     * Return the AgletProxy of a given Aglet.
     * 
     * @param agent
     *            an Aglet
     * @return the AgletProxy representing the given Aglet
     */
    public static AgletProxy getAgletProxy(Aglet agent)
    {
	return agent.getProxy();
    }

    // *** ABOUT MESSAGES ****************************************************
    /**
     * This method sends a message that doesn't expect a reply to a given agent.
     * 
     * @param agent
     *            the Aglet to which the message is sent
     * @param m
     *            the Message being sent
     */
    public static void sendOnewayMessage(AgletProxy agent, Message m)
    {
	try
	{
	    agent.sendOnewayMessage(m);
	}
	catch (InvalidAgletException e)
	{
	    Components.Instance().getLogger().log("ERROR while sending a one-way message: " + e);
	}
    }

    /**
     * This method send a message to an agent.
     * 
     * @param agent
     *            the Aglet being sent to
     * @param m
     *            the Message being sent
     * @return a possible reply on the Message being sent.
     */
    public static Object sendMessage(AgletProxy agent, Message m)
    {
	Object result = null;
	try
	{
	    result = agent.sendMessage(m);
	}
	catch (InvalidAgletException e)
	{
	    Components.Instance().getLogger().log("ERROR sending a message: "
		    + "the given agent does not exist: " + e);
	}
	catch (NotHandledException e)
	{
	    Components.Instance().getLogger().log("ERROR sending a message: The agent "
		    + getAgletClassName(agent)
		    + " cannot react on the given message: \n\t" + e);
	}
	catch (MessageException e)
	{
	    Components.Instance().getLogger().log("ERROR sending a message: " + e);
	}
	return result;
    }

    /**
     * This method sends a reply Object to a Message.
     * 
     * @param msg
     *            the Message replied to.
     * @param arg
     *            the reply Object.
     */
    public static void sendReply(Message msg, Object arg)
    {
	try
	{
	    msg.sendReply(arg);
	}
	catch (IllegalAccessError e)
	{
	    Components.Instance().getLogger().log("ERROR while sending a reply: "
		    + "already replied on this message.");
	}
    }

    /**
     * Send a message to every agent in the host.
     * 
     * @param cxt
     *            the AgletContext in which to perform the multicast.
     * @param msg
     *            the Message to be sent
     * @return a Future ReplySet containing the replies of the agents.
     */
    public static ReplySet sendMulticast(AgletContext cxt, Message msg)
    {
	return cxt.multicastMessage(msg);
    }

    // *** ABOUT PROXIES******************************************************
    /**
     * This method returns the classname of a given AgletProxy.
     * 
     * @param agentProxy
     *            the AgletProxy
     * @return the string representing the agent's classname
     */
    public static String getAgletClassName(AgletProxy agentProxy)
    {
	String result = null;
	try
	{
	    result = agentProxy.getAgletClassName();
	}
	catch (InvalidAgletException e)
	{
	    Components.Instance().getLogger().log("ERROR while getting the aglet's classname: "
		    + e.toString());
	}
	return result;
    }

    /**
     * This method returns the textual message contained within an agent.
     * 
     * @param agentProxy
     *            the AgletProxy
     * @return a String representing the agent's textual message
     */
    public static String getAgletText(AgletProxy agentProxy)
    {
	String text = null;
	try
	{
	    text = agentProxy.getAglet().getText();
	}
	catch (InvalidAgletException e)
	{
	    Components.Instance().getLogger().log("ERROR while getting the aglet's textmessage: "
		    + e.toString());
	}
	return text;
    }

    /**
     * This method returns an AgletID given a specific AgletProxy.
     * 
     * @param agentProxy
     *            the AgletProxy
     * @return the AgletID for the given AgletProxy
     */
    public static AgletID getAgletID(AgletProxy agentProxy)
    {
	AgletID result = null;
	try
	{
	    result = agentProxy.getAgletID();
	}
	catch (InvalidAgletException e)
	{
	    Components.Instance().getLogger().log("ERROR while getting the aglet's ID: "
		    + e.toString());
	}
	return result;
    }

    /**
     * This method returns the AgletInfo of a given AgletProxy.
     * 
     * @param agentProxy
     *            the AgletProxy.
     * @return the AgletInfo for the given AgletProxy.
     */
    public static AgletInfo getAgletInfo(AgletProxy agentProxy)
    {
	AgletInfo info = null;
	try
	{
	    info = agentProxy.getAgletInfo();
	}
	catch (InvalidAgletException e)
	{
	    Components.Instance().getLogger().log("ERROR while getting the aglet's info: "
		    + e.toString());
	}
	return info;
    }

    /**
     * Return the location (String) of the Aglet represented by a given
     * AgletProxy.
     * 
     * @param agent
     *            the AgletProxy
     * @return the String location of the agent
     */
    public static String getAgletLocation(AgletProxy agentProxy)
    {
	String location = null;
	try
	{
	    location = agentProxy.getAddress();
	}
	catch (InvalidAgletException e)
	{
	    Components.Instance().getLogger().log("ERROR while getting the aglet's address: "
		    + e.toString());
	}
	return location;
    }

    /**
     * This method will dispatch a given agent to the given destination.
     * 
     * @param agent
     *            the AgletProxy representing the the agent that is about to
     *            move.
     * @param destination
     *            the String representing the agent's locaton.
     * @return the AgletProxy to the remote agent.
     */
    public static AgletProxy dispatch(AgletProxy agent, String destination)
    {
	URL d = null;
	try
	{
	    d = new URL(destination);
	}
	catch (MalformedURLException e)
	{
	    Components.Instance().getLogger().log("ERROR while dispatching: " + e.toString());
	}
	AgletProxy remoteAgent = null;
	try
	{
	    remoteAgent = agent.dispatch(d);
	}
	catch (Exception e)
	{
	    Components.Instance().getLogger().log("ERROR while dispatching: " + e.toString());
	}
	return remoteAgent;
    }
}
